/*
 * This file is part of Ember.
 *
 * Ember is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Ember is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ember.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) Grant Tyra 2010
 * granttyra@gmail.com
*/

#include "ember_evaluation.h"
#include "ember_call_stack.h"
#include "ember_class.h"
#include "ember_statement.h"
#include "ember_list_type.h"
#include "ember_types.h"
#include "ember_string.h"
#include <stdlib.h>
#include <string.h>

EmberObject *retrieve(const char *expr) {
    return EmberCallStackRetrieve(expr);
}

EmberObject *eval_list(EmberStatement *statement) {
    EmberList *objects = EmberListCreate();
	EmberStatement *substatement;
    int i;
    EmberObject *object;

    for (i = 0; i < statement_sub_count(statement); i++) {
		substatement = statement_sub(statement, i);
        object = EmberEvaluate(substatement);

		if (object->typeFlags & TYPE_EXCEPTION) {
            return object;
        }

        EmberListAdd(objects, object);
    }

    return ember_list_create(objects);
}

EmberObject *eval_dict(EmberStatement *statement) {
    EmberDictionary *dict = EmberDictionaryCreate();
	EmberStatement *keystatement = NULL;
	EmberStatement *valuestatement = NULL;
	EmberObject *keyobj = NULL;
	EmberObject *valueobj = NULL;
    int i;

    for (i = 0; i < statement_sub_count(statement); i += 2) {
		keystatement = statement_sub(statement, i);
		valuestatement = statement_sub(statement, i + 1);

        keyobj = EmberEvaluate(keystatement);

		if (keyobj->typeFlags & TYPE_EXCEPTION) {
            return keyobj;
        }

		valueobj = EmberEvaluate(valuestatement);

		if (valueobj->typeFlags & TYPE_EXCEPTION) {
            return valueobj;
        }

        EmberDictionaryPut(dict, EmberStringValue(keyobj), valueobj);

		EmberObjectRelease(keyobj);
		EmberObjectRetain(valueobj);
    }

    return ember_dict_create(dict);
}

EmberObject *eval_value(EmberStatement *statement) {
    char *expr, *sval;
    
    if (statement_sub_count(statement) < 1) {
		return EmberObjectCreateException(EmberStringConcat("Expected symbol to evaluate: ", statement->code));
    }

    expr = statement_raw(statement_sub(statement, 0));

	if (strcmp(expr, "false") == 0) {
		return EmberObjectCreateInteger(0);
	}

	if (strcmp(expr, "true") == 0) {
		return EmberObjectCreateInteger(1);
	}

	if (strcmp(expr, "null") == 0) {
		return EmberNull();
	}

	if (EmberStringCount(expr, '"') >= 2) {
        sval = EmberStringBetween(expr, '"', '"');
		return EmberObjectCreateString(sval);
    }

    if (EmberStringIsValidInteger(expr)) {
        return EmberObjectCreateInteger(atoi(expr));
    }
		
	if (EmberStringIsValidDecimal(expr)) {
        return EmberObjectCreateDecimal((float)atof(expr));
    }

    return retrieve(expr);
}

EmberObject *call_c_function(void *function) {
    EmberObject *(*cfunc)() = function;
	return (*cfunc)();
}

EmberObject *EmberCallMethod(EmberObject *obj, const char *method_name, EmberList *args) {
    EmberMethod *method = EmberDictionaryGet(obj->classdef->instMethods, method_name);
    EmberStackFrame *frame = EmberStackFrameCreate();
    EmberObject *result, *argument;
	int i;
    
    for (i = 0; i < method->arguments->size; i++) {
		argument = EmberEvaluate(EmberListGet(args, i));

		if (argument->typeFlags & TYPE_EXCEPTION) {
			return argument;
		}

		EmberStackFrameBind(frame, EmberListGet(method->arguments, i), argument);
    }

    EmberCallStackPush(frame);

    if (method->isNative) {
        result = call_c_function(method->function);
    } else {
        result = EmberEvaluate(method->function);
    }

    EmberCallStackPop();
    EmberStackFrameDestroy(frame);
    return result;
}
 

EmberObject *eval_call_instance(EmberStatement *statement, EmberObject *obj, const char *method_name) {
	EmberMethod *method; 
    EmberStackFrame *frame; 
	EmberObject *retvalue, *argument;
	int i, argcount;

    method = EmberClassGetInstanceMethod(obj->classdef, method_name);

    if (method == NULL) {
		return EmberObjectCreateException(EmberStringConcat("Method does not exist: ", statement->code));
    }

	argcount = statement_sub_count(statement) - 2;

    if (method->arguments->size != argcount) {
		return EmberObjectCreateException(EmberStringConcat("Incorrect number of arguments supplied:", statement->code));
    }

    frame = EmberStackFrameCreate();
	frame->classdef = obj->classdef;
	EmberStackFrameBind(frame, "this", obj);

    for (i = 0; i < method->arguments->size; i++) {
		argument = EmberEvaluate(statement_sub(statement, i + 2));

		if (argument->typeFlags & TYPE_EXCEPTION) {
			return argument;
		}

		EmberStackFrameBind(frame, EmberListGet(method->arguments, i), argument);
    }

	EmberCallStackPush(frame);

	if (method->isNative) {
        retvalue = call_c_function(method->function);
    } else {
        retvalue = EmberEvaluate(method->function);
    }

	EmberCallStackPop();
	EmberStackFrameDestroy(frame);
	frame = NULL;
    
	return retvalue;
}

EmberObject *eval_call_class(EmberStatement *statement, EmberClassDef *classdef, const char *method_name) {
	EmberMethod *method; 
    EmberStackFrame *frame; 
	EmberObject *retvalue, *argument;
	int i;

    method = EmberClassGetStaticMethod(classdef, method_name);

    if (method == NULL) {
		return EmberObjectCreateException(EmberStringConcat("Method does not exist: ", statement->code));
    }

    if (method->arguments->size != statement_sub_count(statement) - 2) {
		return EmberObjectCreateException(EmberStringConcat("Incorrect number of arguments supplied:", statement->code));
    }

	frame = EmberStackFrameCreate();
	frame->classdef = classdef;

    for (i = 0; i < method->arguments->size; i++) {
		argument = EmberEvaluate(statement_sub(statement, i + 2));

		if (argument->typeFlags & TYPE_EXCEPTION) {
			return argument;
		}

        EmberStackFrameBind(frame, EmberListGet(method->arguments, i), EmberEvaluate(statement_sub(statement, i + 2)));
    }

    
	EmberCallStackPush(frame);

	if (method->isNative) {
        retvalue = call_c_function(method->function);
    } else {
        retvalue = EmberEvaluate(method->function);
    }

	EmberCallStackPop();
	EmberStackFrameDestroy(frame);
    frame = NULL;
	return retvalue;
}

EmberObject *eval_call(EmberStatement *statement) {
    char *method_name;
    EmberObject *receiver;
    
    if (statement_sub_count(statement) < 2) {
		return EmberObjectCreateException(EmberStringConcat("Instance method call requires object and method name: ", statement->code));
    }

	method_name = statement_raw(statement_sub(statement, 1));
	receiver = EmberEvaluate(statement_sub(statement, 0));

	if (receiver->typeFlags & TYPE_EXCEPTION) {
		return receiver;
	}

	return eval_call_instance(statement, receiver, method_name);
}

EmberObject *eval_static(EmberStatement *statement) {
    char *method_name, *class_name;
	EmberClassDef *classdef;
    
    if (statement_sub_count(statement) < 2) {
		return EmberObjectCreateException(EmberStringConcat("Static method call requires class and method name:", statement->code));
    }

	class_name = statement_raw(statement_sub(statement, 0));
	method_name = statement_raw(statement_sub(statement, 1));
	classdef = EmberClassMapGet(class_name);

	if (classdef == NULL) {
		return EmberObjectCreateException(EmberStringConcat("Undefined class type: ", statement->code));
	}

	return eval_call_class(statement, classdef, method_name);;
}

EmberObject *eval_assign(EmberStatement *statement) {
    char *name = NULL;
    EmberObject *obj;
    EmberStatement *substatement = NULL;
    
    if (statement_sub_count(statement) < 2) {
		return EmberObjectCreateException(EmberStringConcat("Object assignment requires symbol and expression: ", statement->code));
    }

    substatement = statement_sub(statement, 1);
    name = statement_raw(statement_sub(statement, 0));
    
	obj = EmberEvaluate(substatement);
	
	if (obj->typeFlags & TYPE_EXCEPTION){
		return obj;
	}

	EmberCallStackBind(name, obj);
    return obj;
}

EmberObject *eval_if(EmberStatement *statement) {
	EmberStatement *expression, *subs;
	EmberObject *result;
	int value;
    
    if (statement_sub_count(statement) < 2) {
		return EmberObjectCreateException(EmberStringConcat("If statement requires an expression: ", statement->code));
    }

	expression = statement_sub(statement, 0);
	subs = statement_sub(statement, 1);
	result = EmberEvaluate(expression);

	value = EmberIntegerValue(result);
	if (value > 0) {
		return EmberEvaluate(subs);
	} else if (statement_sub_count(statement) > 2){
		return EmberEvaluate(statement_sub(statement, 2));
	} else {
		return result;
	}
}

EmberObject *eval_block(EmberStatement *statement, int *should_break) {
	int i = 0;
	int count = 0;
    EmberObject *result = NULL;
    EmberStatement *substatement = NULL;

	count = statement_sub_count(statement);
	result = NULL;

    for (i = 0; i < count; i++) {
        substatement = statement_sub(statement, i);

		if (substatement->type == STATEMENT_BREAK) {
			*should_break = 1;
			return result;
		}

		if (substatement->type == STATEMENT_CONTINUE) {
			*should_break = 0;
			return result;
		}
		
        result = EmberEvaluate(substatement);

		if (substatement->type == STATEMENT_RETURN || (result->typeFlags & TYPE_EXCEPTION)) {
			*should_break = 1;
			return result;
		}
    }

    return result;
}

EmberObject *eval_while(EmberStatement *statement) {
	EmberStatement *expression, *subs;
	EmberObject *result = NULL;
   	int should_break = 0;
 
    if (statement_sub_count(statement) < 2) {
		return EmberObjectCreateException(EmberStringConcat("While loop requires an expression: ", statement->code));
    }

	expression = statement_sub(statement, 0);
	subs = statement_sub(statement, 1);

	while (EmberIntegerValue(EmberEvaluate(expression)) > 0) {
		should_break = 0;
		result = eval_block(subs, &should_break); 

		if (result->typeFlags & TYPE_EXCEPTION) {
			return result;
		}

		if (should_break) {
			return result;
		}
	}

	return EmberNull();
}

EmberObject *eval_for(EmberStatement *statement) {
	EmberStatement *pre, *check, *post, *subs;
	EmberObject *result, *pre_result, *check_result, *post_result;
	int check_flag = 0;
	int should_break = 0;
    
    if (statement_sub_count(statement) < 4) {
		return EmberObjectCreateException(EmberStringConcat("For loop requires three expressions: ", statement->code));
    }

	pre = statement_sub(statement, 0);
	check = statement_sub(statement, 1);
	post = statement_sub(statement, 2);
	subs = statement_sub(statement, 3);

	pre_result = EmberEvaluate(pre);

	if (pre_result->typeFlags & TYPE_EXCEPTION) {
		return pre_result;
	}

	while (1) {
		check_result = EmberEvaluate(check);

		if (check_result->typeFlags & TYPE_EXCEPTION) {
			return check_result;
		}

		check_flag = EmberIntegerValue(check_result);

		if (check_flag) {
			should_break = 0;
			result = eval_block(subs, &should_break);

			if (result->typeFlags & TYPE_EXCEPTION) {
				return result;
			}

			if (should_break) {
				return result;
			}

			post_result = EmberEvaluate(post);

			if (post_result->typeFlags & TYPE_EXCEPTION) {
				return post_result;
			}
		} else {
			break;
		}
	}

	return result;
}

EmberObject *eval_new(EmberStatement *statement) {
	char *class_name;
	EmberObject *obj;
    
    if (statement_sub_count(statement) == 0) {
		return EmberObjectCreateException(EmberStringConcat("New statement requires class name: ", statement->code));
    }

	class_name = statement_raw(statement_sub(statement, 0));
	obj = EmberObjectCreate(class_name);
    
    if (obj == NULL) {
		return EmberObjectCreateException(EmberStringConcat("Unable to create object of type: ", statement->code));
	}
    
	return obj;
}

EmberObject *eval_return(EmberStatement *statement) {
	EmberStatement *expression;
    
    if (statement_sub_count(statement) == 0) {
		return EmberObjectCreateException(EmberStringConcat("Return statement requires expression: ", statement->code));
    }
    
	expression = statement_sub(statement, 0);
	return EmberEvaluate(expression);
}

EmberObject *eval_root(EmberStatement *statement) {
	int i;
    EmberObject *result;
    EmberStatement *substatement;

	result = NULL;

    for (i = 0; i < statement_sub_count(statement); i++) {
        substatement = statement_sub(statement, i);
        result = EmberEvaluate(substatement);

		if (substatement->type == STATEMENT_RETURN || (result->typeFlags & TYPE_EXCEPTION)) {
			return result;
		}
    }

    return result;
}

EmberObject *EmberEvaluate(EmberStatement *statement) {
    switch (statement->type) {
        case STATEMENT_ROOT:
            return eval_root(statement);
        case STATEMENT_ASSIGN:
            return eval_assign(statement);
        case STATEMENT_CALL:
            return eval_call(statement);
		case STATEMENT_STATIC:
			return eval_static(statement);
        case STATEMENT_VALUE:
            return eval_value(statement);
		case STATEMENT_IF:
			return eval_if(statement);
		case STATEMENT_WHILE:
			return eval_while(statement);
		case STATEMENT_FOR:
			return eval_for(statement);
		case STATEMENT_NEW:
			return eval_new(statement);
		case STATEMENT_RETURN:
			return eval_return(statement);
		case STATEMENT_LIST:
			return eval_list(statement);
		case STATEMENT_DICT:
			return eval_dict(statement);
        default:
            return NULL;
    }
}

